var SecureNetworkbeacon = require("./proxyPdu.js").SecureNetworkbeacon
var ConfigModelAppBind = require("./opcode.js").ConfigModelAppBind
var ConfigNetKeyUpdate = require("./opcode.js").ConfigNetKeyUpdate
var ConfigNetKeyAdd = require("./opcode.js").ConfigNetKeyAdd
var ConfigNetKeyDelete = require("./opcode.js").ConfigNetKeyDelete
var ConfigAppKeyAdd = require("./opcode.js").ConfigAppKeyAdd
var ConfigKeyRefreshPhaseSet = require("./opcode.js").ConfigKeyRefreshPhaseSet
var CRYPTO = require("./crypto/crypto.js")
var Provisioning = require("./provisioning.js")
var utils = require("./util.js")
var BleCommand = require("./bleCommand.js")
var crypto = new CRYPTO()
var ECDH = require("./ecdh.js")
var jsbn2 = require("./ecdh/jsbn2.js")
var BigInteger = jsbn2.BigInteger
var ProxyPdu = require("./proxyPdu.js").ProxyPdu
var LightControl = require("./lightControl.js")
var EventEmitter = require("./event.js")
var AsyncProcess = require("./asyncProcess.js")
var MeshBeacon = require("./meshBeacon.js")
var Modal = require("../../utils/modal.js")
var getResponseMessage = require("./code.js").getResponseMessage


var MESH_PROXY_SERVICE = '00001828-0000-1000-8000-00805F9B34FB';
var Mesh_Provisioning_Service = '00001827-0000-1000-8000-00805F9B34FB';
var MESH_PROXY_DATA_IN = '00002ADD-0000-1000-8000-00805F9B34FB';
var MESH_PROXY_DATA_OUT = '00002ADE-0000-1000-8000-00805F9B34FB';
var MESH_Provisioning_DATA_IN = '00002ADB-0000-1000-8000-00805F9B34FB';
var MESH_Provisioning_DATA_OUT = '00002ADC-0000-1000-8000-00805F9B34FB';

var modelIds = ["0001005d"];

class OpenBluetoothAdapter extends AsyncProcess {
  constructor(blemash) {
    super();
    this.available = false;
    this.blemash = blemash;
  }
  process() {
    var self = this;
    if (self.available) {
      self.nextProcess();
    } else {
      wx.openBluetoothAdapter({
        success: (res) => {
          self.available = true;
          self.nextProcess();
        },
        fail: (res) => self.error(res)
      })
    }
  }
}

class StartBluetoothDevicesDiscovery extends AsyncProcess {
  constructor(blemash) {
    super();
    this.discoveryStarted = false;
    this.blemash = blemash;
  }
  process() {
    var self = this;
    if (self.discoveryStarted) {
      self.nextProcess();
    } else {
      self.discoveryStarted = true;
      wx.startBluetoothDevicesDiscovery({
        allowDuplicatesKey: true,
        services: [Mesh_Provisioning_Service],
        success: (res) => self.nextProcess(),
        fail: (res) => self.error(res)
      });
    }
  }
}

class OnBluetoothDeviceFound extends AsyncProcess {
  constructor(blemash) {
    super();
    this.devices = [];
    this.blemash = blemash;
  }
  process() {
    var self = this;
    self.devices = [];
    wx.onBluetoothDeviceFound((res) => {
      res.devices.forEach(device => {
        if (self.devices.indexOf(device.deviceId) === -1) {
          self.nextProcess(device);
          self.devices.push(device.deviceId);
        }
      })
    })
  }
}

class CreateBLEConnection extends AsyncProcess {
  constructor(blemash) {
    super();
    this.connected = false;
    this.deviceId;
    this.blemash = blemash;
  }
  process() {
    var self = this;
    if (self.connected) {
      self.nextProcess(self.deviceId);;
    } else {
      wx.createBLEConnection({
        deviceId: self.deviceId,
        success: (res) => self.success(),
        fail: (res) => {
          if (res.errCode==-1&&res.errMsg=="createBLEConnection:fail:already connect"){
            self.success();
          }else{
            console.log("createBLEConnection", self.deviceId, "fail", res);
            self.error(res)
          }
        }
      })
    }
  }
  success(){
    console.log("createBLEConnection", this.deviceId, "success");
    this.connected = true;
    this.nextProcess(this.deviceId);
    this.blemash.deviceId = this.deviceId
  }
  setDeviceId(deviceId) {
    this.deviceId = deviceId;
  }
}

class CloseBLEConnection extends AsyncProcess {
  constructor(blemash) {
    super();
    this.status;
    this.blemash = blemash;
  }
  process() {
    var self = this;
    if (self.blemash.deviceId) {
      wx.closeBLEConnection({
        deviceId: self.blemash.deviceId,
        success: () => {
          self.success();
        },
        fail: (res) => {
          if (res.errCode==10006){
            self.success();
          }else{
            self.error(res)
          }
        }
      });
    } else {
      self.nextProcess();
    }
  }
  success() {
    console.log("closeBLEConnection success");
    this.blemash.init();
    this.nextProcess();
  }
}

class BLEDeviceServices extends AsyncProcess {
   constructor(service, blemash) {
    super();
    this.service = service;
    this.blemash = blemash;
  }
  process(deviceId) {
    var self = this;
    deviceId = deviceId || self.blemash.deviceId
    wx.getBLEDeviceServices({
      deviceId: deviceId,
      success: (res) => {
        for (let i = 0; i < res.services.length; i++) {
          if (res.services[i].uuid == self.service) {
            console.log('getBLEDeviceServices success', res.services[i])
            self.nextProcess(deviceId);
            return;
          }
        }
      },
      fail: (res) => self.error(res)
    })
  }
}

class BLEDeviceCharacteristics extends AsyncProcess {
  constructor(service, data_in, data_out) {
    super();
    this.service = service;
    this.data_in = data_in;
    this.data_out = data_out;
    this.has_data_in = false;
    this.has_data_out = false;
  }
  process(deviceId) {
    var self = this;
    wx.getBLEDeviceCharacteristics({
      deviceId: deviceId,
      serviceId: self.service,
      success: (res) => {
        console.log('getBLEDeviceCharacteristics success', res)
        for (let i = 0; i < res.characteristics.length; i++) {
          let item = res.characteristics[i]
          if (item.uuid == self.data_out) {
            self.has_data_out = true;
          }
          if (item.uuid == self.data_in) {
            self.has_data_in = true;
          }
        }
        if (!self.has_data_out || !self.has_data_in) {
          self.fail("getBLEDeviceCharacteristics fail");
        } else self.nextProcess(deviceId);
      },
      fail: (res) => self.error(res)
    })
  }
}

class NotifyValueChange extends AsyncProcess {
  constructor(blemash, service, data_out) {
    super();
    this.blemash = blemash;
    this.service = service;
    this.data_out = data_out;
  }
  process(deviceId) {
    var self = this;
    wx.notifyBLECharacteristicValueChange({
      state: true,
      deviceId: deviceId,
      serviceId: self.service,
      characteristicId: self.data_out,
      success: (res) => {
        wx.onBLECharacteristicValueChange(function (res) {
          var hexString = utils.u8AToHexString(new Uint8Array(res.value));
          ProxyPdu.prototype.parse(hexString);
        })
        self.nextProcess(deviceId)
      },
      fail: (res) => self.error(res)
    })
  }
}

class Blemash extends EventEmitter {

  constructor(meshId, netkey, netkeyIndex, appkey, appkeyIndex, seq_number, iv_index, src, ttl) {
    super();
    this.meshId = meshId;
    this.netkey = netkey;
    this.appkey = appkey;
    this.netkeyIndex = netkeyIndex;
    this.appkeyIndex = appkeyIndex;
    this.seq_number = seq_number;
    this.iv_index = iv_index;
    this.src = src;
    this.ttl = ttl;



    this.meshBeacon = new MeshBeacon(this);
    this.bleCommand = new BleCommand(this);
    this.provisioning = new Provisioning(this);
    this.lightControl = new LightControl(this, this.bleCommand);
    ProxyPdu.prototype.registerMeshBeaconListener(this.meshBeacon);
    ProxyPdu.prototype.registerBleCommandListener(this.bleCommand);
    ProxyPdu.prototype.registerProvisioningListener(this.provisioning);

    this.openBluetoothAdapter = new OpenBluetoothAdapter(this);
    this.startBluetoothDevicesDiscovery = new StartBluetoothDevicesDiscovery(this);
    this.onBluetoothDeviceFound = new OnBluetoothDeviceFound(this);
    this.createBLEConnection = new CreateBLEConnection(this);
    this.closeBLEConnection = new CloseBLEConnection(this);

    this.getMESH_PROXY_SERVICE = new BLEDeviceServices(MESH_PROXY_SERVICE, this);
    this.getMesh_Provisioning_Service = new BLEDeviceServices(Mesh_Provisioning_Service, this);

    this.getMESH_PROXYCharacteristics = new BLEDeviceCharacteristics(MESH_PROXY_SERVICE, MESH_PROXY_DATA_IN, MESH_PROXY_DATA_OUT);
    this.getMesh_ProvisioningCharacteristics = new BLEDeviceCharacteristics(Mesh_Provisioning_Service,
      MESH_Provisioning_DATA_IN, MESH_Provisioning_DATA_OUT);
    this.notifyProxyValueChange = new NotifyValueChange(this, MESH_PROXY_SERVICE, MESH_PROXY_DATA_OUT);
    this.notifyProvisionValueChange = new NotifyValueChange(this, Mesh_Provisioning_Service, MESH_Provisioning_DATA_OUT);

  }
  static getMeshNetKey(){
    var newNetKey = utils.randomString(32);
    var netId = crypto.k3(newNetKey);
    if (netId.length % 2 != 0) return this.getMeshNetKey();
    return newNetKey;
  }
  init() {
    this.openBluetoothAdapter.available = false;
    this.startBluetoothDevicesDiscovery.discoveryStarted = false;
    this.createBLEConnection.connected = false;
    this.getMESH_PROXYCharacteristics.has_data_in = false;
    this.getMESH_PROXYCharacteristics.has_data_out = false;
    this.getMesh_ProvisioningCharacteristics.has_data_in = false;
    this.getMesh_ProvisioningCharacteristics.has_data_out = false;
  }

  close(callback) {
    this.disconnectListen(null);
    this.openBluetoothAdapter
      .thenApply(this.closeBLEConnection)
      .catch((error) =>{
        this.fail(error, callback);
      }) 
      .whenComplete(() => {
        if (callback) callback(getResponseMessage(0));
      })
    AsyncProcess.run(this.openBluetoothAdapter);
  }

  closeBluetoothAdapter() {
    var self = this;
    wx.closeBluetoothAdapter({
      success: (res) => {
        self.openBluetoothAdapter.discoveryStarted = false;
      }
    })
  }

  scan(callback) {
    this.openBluetoothAdapter
      .thenApply(this.startBluetoothDevicesDiscovery)
      .thenApply(this.onBluetoothDeviceFound)
      .whenComplete(callback)
      .catch((error) => {
        if (error.errCode == 10006) {
          this.deviceId = null;
          this.scan(callback);
        } else {
          this.fail(error, callback)
        }
      });
    AsyncProcess.run(this.openBluetoothAdapter);
  }

  stopScan(callback) {
    var self = this;
    wx.stopBluetoothDevicesDiscovery({
      success: (res) => {
        self.startBluetoothDevicesDiscovery.discoveryStarted = false;
        self.createBLEConnection.connected = false;
        if (callback) callback(getResponseMessage(0))
      }
    });
  }

  findProxy(deviceUUID, callback) {
    if(this.hasProxy()) return;
    let process;
    if (this.deviceId && this.deviceId == deviceUUID) {
      process = this.openBluetoothAdapter;
    } else {
      this.createBLEConnection.setDeviceId(deviceUUID);
      this.openBluetoothAdapter
        .thenApply(this.closeBLEConnection)
        .thenApply(this.createBLEConnection);
      process = this.createBLEConnection;
    }
    process.thenApply(this.getMESH_PROXY_SERVICE)
      .thenApply(this.getMESH_PROXYCharacteristics)
      .thenApply(this.notifyProxyValueChange)
      .catch((error) =>this.fail(error, callback))
      .whenComplete(() => {
        this.proxyuuid = deviceUUID;
        this.closeBLEConnection.proxy_status = true;
        this.keyRefresh(0, 0, this.netkey, this.iv_index);
        if (callback) callback(getResponseMessage(0));
      });
    this.closeBLEConnection.proxy_status = false;
    AsyncProcess.run(this.openBluetoothAdapter);
  }

  addDevice(deviceUUID, address, callback) {
    this.provisioning_cancle=false;
    let process;
    if (this.deviceId && this.deviceId == deviceUUID) {
      process = this.openBluetoothAdapter;
    }else{
      this.closeBLEConnection.proxy_status = false;
      this.createBLEConnection.setDeviceId(deviceUUID);
      this.openBluetoothAdapter
        .thenApply(this.closeBLEConnection)
        .thenApply(this.createBLEConnection);
      process = this.createBLEConnection;
    }
    process.thenApply(this.getMesh_Provisioning_Service)
      .thenApply(this.getMesh_ProvisioningCharacteristics)
      .thenApply(this.notifyProvisionValueChange)
      .catch((error) => this.fail(error, callback))
      .whenComplete(() => {
        this.emit("provisioning", getResponseMessage(0, "connected device"));
        this.provisioning.addDevice(address);
      })
    this.off("provisioning");
    if (callback) {
      this.on("provisioning", callback);
      this.emit("provisioning", getResponseMessage(0, "connecting device"));
    }
    if (this.provisioning_cancle) return;
    AsyncProcess.run(this.openBluetoothAdapter);
  }

  //连接代理
  hasProxy() {
    return this.getMESH_PROXYCharacteristics.has_data_out && this.getMESH_PROXYCharacteristics.has_data_in;
  }

  isConnected() {
    return this.createBLEConnection.connected;
  }

  proxyOperate(operate, callback) {
    if (!this.hasProxy()) {
      Modal.showErr(getResponseMessage("01").message);
    } else {
      operate();
    }
  }

  removeNode(dst, deviceKey, deviceUUID,callback,times) {
    if(times==null) times=3;
    this.proxyOperate(() => {
      var newNetKey = Blemash.getMeshNetKey();
      var configNetKeyUpdate = new ConfigNetKeyUpdate();
      configNetKeyUpdate.NetKeyIndex = this.netkeyIndex;
      configNetKeyUpdate.NetKey = newNetKey;
      this.bleCommand.setDevKey(deviceKey);
      configNetKeyUpdate.send(this.bleCommand, dst)
        .then((op) => {
          this.keyRefresh(0, 0, newNetKey, this.iv_index);
          if (this.proxyuuid == deviceUUID) this.init();
          callback(getResponseMessage(0));
        })
        .catch((error) => {
          times--;
          if(error.code=="ff"&&times!=0){
            this.removeNode(dst, deviceKey, deviceUUID, callback,times) 
          }else{
            callback(error)
          }
        });
    }, callback)
  }

  configure(deviceKey, address, callback) {
    this.proxyOperate(() => {
      this.bleCommand.setDevKey(deviceKey);
      var configAppKeyAdd = new ConfigAppKeyAdd();
      configAppKeyAdd.NetKeyIndex = this.netkeyIndex;
      configAppKeyAdd.AppKeyIndex = this.appkeyIndex;
      configAppKeyAdd.AppKey = this.appkey;

      var configModelAppBind = new ConfigModelAppBind();
      configModelAppBind.ElementAddress = address;
      configModelAppBind.AppKeyIndex = this.appkeyIndex;
      configModelAppBind.ModelIdentifier = modelIds[0];

      configAppKeyAdd.send(this.bleCommand, address)
        .then(configModelAppBind)
        .then((op) => {
          callback(getResponseMessage(0));
        })
        .catch((error) => {
          callback(error);
        });
    }, callback)
  }


  addressControl(lightAddress, cold, warm, red, green, blue, callback) {
    this.proxyOperate(() => {
      this.lightControl.lightControl(lightAddress, cold, warm, red, green, blue);
      if (callback) this.once("lightControl", callback);
    }, callback)
  }
  closeLight(lightAddress, callback) {
    this.proxyOperate(() => {
      this.lightControl.closeLight(lightAddress);
      if (callback) this.once("closeLight", callback);
    }, callback)
  }
  groupControl(groupAddress, cold, warm, red, green, blue, callback) {
    this.proxyOperate(() => {
      this.lightControl.groupControl(groupAddress, cold, warm, red, green, blue);
      if (callback) callback();
    }, callback)
  }
  allControl(cold, warm, red, green, blue, callback) {
    this.proxyOperate(() => {
      this.lightControl.groupControl("ffff", cold, warm, red, green, blue);
      if (callback) callback();
    }, callback)
  }
  groupCloseLight(groupAddress, callback) {
    this.proxyOperate(() => {
      this.lightControl.groupCloseLight(groupAddress);
      if (callback) callback();
    }, callback)

  }
  allCloseLight(callback) {
    this.proxyOperate(() => {
      this.lightControl.allCloseLight();
      if (callback) callback();
    }, callback)
  }

  //配置遥控器
  remoteConfig(dst, groupAddressList, callback) {
    if (!groupAddressList instanceof Array) {
      if (callback) callback(getResponseMessage("08", "groupAddressList不是一个数组"));
      return;
    }
    var self=this;
    let groupAddress = groupAddressList.shift();
    let COLD = "55";
    let WARN = "AA";
    let RED = "00";
    let GREEN = groupAddress.substr(0, 2);
    let BLUE = groupAddress.substr(2);
    self.addressControl(dst, COLD, WARN, RED, GREEN, BLUE, function (result) {
      if (result.code == 0) {
        if (groupAddressList.length > 0) self.remoteConfig(dst, groupAddressList, callback);
        else callback(getResponseMessage(0));
      } else {
        if (callback) callback(getResponseMessage("07", "配置遥控器失败"));
      }
    });
  }

  addGroup(groupAddress, dst, deviceKey, callback) {
    this.proxyOperate(() => {
      this.bleCommand.setDevKey(deviceKey);
      this.lightControl.addGroup(groupAddress, dst);
      if (callback) this.once("addGroup", callback);
    }, callback)
   
  }

  removeGroup(groupAddress, dst, deviceKey, callback) {
    this.proxyOperate(() => {
      this.bleCommand.setDevKey(deviceKey);
      this.lightControl.removeGroup(groupAddress, dst);
      if (callback) this.once("removeGroup", callback);
    }, callback)
  }

  getMeshStatus(callback) {
    this.proxyOperate(() => {
      this.lightControl.getMeshStatus();
      this.off("meshStatus");
      if (callback) this.on("meshStatus", callback);
    }, callback)
  }

  ivUpdate() {
    this.proxyOperate(() => {
      var iv_index = utils.toHex(parseInt(this.iv_index, 16) + 1, 4);
      this.iv_index = iv_index;
      this.keyRefresh(0, 1, this.netkey, this.iv_index);
      this.emit("ivIndexChange",this.meshId, this.iv_index);
    })
  }


  keyRefresh(KeyRefreshFlag, IVUpdateFlag, netKey, iv_index) {
    let secureNetworkbeacon = new SecureNetworkbeacon();
    secureNetworkbeacon.KeyRefreshFlag = KeyRefreshFlag;
    secureNetworkbeacon.IVUpdateFlag = IVUpdateFlag;
    secureNetworkbeacon.networkID = crypto.k3(netKey);
    secureNetworkbeacon.iVIndex = iv_index;
    secureNetworkbeacon.beaconKey = crypto.generateBeaconKey(netKey);
    this.meshBeacon.sendSecureNetworkbeacon(secureNetworkbeacon)
  }

  fail(res, callback) {
    var self = this;
    switch (res.errCode) {
      case 10000:
        if (callback) callback(getResponseMessage(0))
        break;
      case 10001:
        if (callback) callback(getResponseMessage("02"))
        break;
      case 10002:
        if (callback) callback(getResponseMessage("03"))
        break;
      case 10003:
        if (callback) callback(getResponseMessage("06"))
        break;
      case 10004:
        if (callback) callback(getResponseMessage("09"))
        break;
      case 10005:
        if (callback) callback(getResponseMessage("10"))
        break;
      case 10006:
        if (callback) callback(getResponseMessage("04"))
        break;
      case 10009:
        if (callback) callback(getResponseMessage("05"))
        break;
      case 10012:
        if (callback) callback(getResponseMessage("08"))
        break;
      default:
        console.log(res);
        if (callback) callback(getResponseMessage("99"))
        break;
    }
  }

  disconnectListen(callback) {
    var self = this;
    wx.onBLEConnectionStateChange(function (res) {
      if (!self.closeBLEConnection.proxy_status){
        return;
      } 
      if (self.createBLEConnection.connected && !res.connected) {
        self.init();
        if (callback) {
          console.log("connectionStateChangeCallback:", res);
          callback();
        }
      }
    })
  }

  writeProxyValue(value) {
    var self = this;
    if (!self.writeProxyBuffer) {
      self.writeProxyBuffer = self.writeProxyBuffer || [];
    }
    self.writeProxyBuffer.push(value);
    if (!self.writeProxyProcess) {
      self.writeProxyProcess = setInterval(function () {
        if (self.writeProxyBuffer && self.writeProxyBuffer.length == 0) {
          clearInterval(self.writeProxyProcess);
          self.writeProxyProcess = null;
          return;
        }
        if (!self.inWriteProxyProcess) {
          let v = self.writeProxyBuffer.shift();
          self.writeProxy(v);
        }
      }, 10);
    }
  }

  writeProxy(value, i) {
    var self = this;
    self.inWriteProxyProcess = true;
    i = i || 0;
    if (i == value.length) {
      self.inWriteProxyProcess = false;
      return;
    }
    var array = utils.hexToU8A(value[i]);
    wx.writeBLECharacteristicValue({
      deviceId: self.deviceId,
      serviceId: MESH_PROXY_SERVICE,
      characteristicId: MESH_PROXY_DATA_IN,
      value: array.buffer,
      success: (res) => {
        if (res.errCode == 0 && i < value.length) {
          i++;
        }
        self.writeProxy(value, i);
      }
    })
  }

  writeProvisionValue(provisioningProcess, value, i) {
    var self = this;
    self.inWriteProvisionProcess = true;
    i = i || 0;
    if (i == value.length) {
      provisioningProcess.nextProcess();
    } else {
      var array = utils.hexToU8A(value[i]);
      wx.writeBLECharacteristicValue({
        deviceId: self.deviceId,
        serviceId: Mesh_Provisioning_Service,
        characteristicId: MESH_Provisioning_DATA_IN,
        value: array.buffer,
        success: (res) => {
          if (res.errCode == 0 && i < value.length) {
            i++;
          }
          self.writeProvisionValue(provisioningProcess, value, i);
        }
      })
    }
  }

  onBleMashSeqChange(fn) {
    this.off("seqChange");
    this.on("seqChange", fn);
  }

  onBleMashIvIndexChange(fn) {
    this.off("ivIndexChange");
    this.on("ivIndexChange", fn);
  }

  onBleMashNetKeyChange(fn) {
    this.on("netKetChange", fn);
  }
}


module.exports = Blemash;
